#pragma once

#include "State.h"

#include "Behavior.h"
#include "Classifier.h"
#include "ConnectionPointReference.h"
#include "Constraint.h"
#include "Pseudostate.h"
#include "RedefinableElement.h"
#include "Region.h"
#include "StateMachine.h"
#include "Trigger.h"

namespace uml {

const bool State::isComposite_default_ = false;
const bool State::isOrthogonal_default_ = false;
const bool State::isSimple_default_ = false;
const bool State::isSubmachineState_default_ = false;
State::State(const QString& umlType)
    : NamedElement(umlType) {}

void State::fromJson(const QJsonObject& json, QStringList& errorList) {
    if (json.isEmpty()) {
        return;
    }

    NamedElement::fromJson(json, errorList);
    Namespace::fromJson(json, errorList);
    RedefinableElement::fromJson(json, errorList);
    Vertex::fromJson(json, errorList);
    JsonSerializable::fromJsonArray(connections_, "connections", json, errorList);
    JsonSerializable::fromJsonArray(connectionPoints_, "connectionPoints", json, errorList);
    JsonSerializable::fromJsonArray(deferrableTriggers_, "deferrableTriggers", json, errorList);
    doActivity_ = JsonSerializable::create<Behavior>(json.value("doActivity"), errorList);

    entry_ = JsonSerializable::create<Behavior>(json.value("entry"), errorList);

    exit_ = JsonSerializable::create<Behavior>(json.value("exit"), errorList);

    isComposite_ = json.value("isComposite").toBool(isComposite_default_);
    isOrthogonal_ = json.value("isOrthogonal").toBool(isOrthogonal_default_);
    isSimple_ = json.value("isSimple").toBool(isSimple_default_);
    isSubmachineState_ = json.value("isSubmachineState").toBool(isSubmachineState_default_);
    redefinedState_ = JsonSerializable::create<State>(json.value("redefinedState"), errorList);

    redefinitionContext_ = JsonSerializable::create<Classifier>(json.value("redefinitionContext"), errorList);

    JsonSerializable::fromJsonArray(regions_, "regions", json, errorList);
    stateInvariant_ = JsonSerializable::create<Constraint>(json.value("stateInvariant"), errorList);

    submachine_ = JsonSerializable::create<StateMachine>(json.value("submachine"), errorList);

}

void State::toJson(QJsonObject& json) {
    NamedElement::toJson(json);
    Namespace::toJson(json);
    RedefinableElement::toJson(json);
    Vertex::toJson(json);
    JsonSerializable::toJsonArray(connections_, "connections", json);
    JsonSerializable::toJsonArray(connectionPoints_, "connectionPoints", json);
    JsonSerializable::toJsonArray(deferrableTriggers_, "deferrableTriggers", json);
    if (doActivity_) {
        QJsonObject doActivity_json;
        doActivity_->toJson(doActivity_json);
        json.insert("doActivity", doActivity_json);
    }
    if (entry_) {
        QJsonObject entry_json;
        entry_->toJson(entry_json);
        json.insert("entry", entry_json);
    }
    if (exit_) {
        QJsonObject exit_json;
        exit_->toJson(exit_json);
        json.insert("exit", exit_json);
    }
    if (isComposite_ != isComposite_default_) {
        json.insert("isComposite", isComposite_);
    }
    if (isOrthogonal_ != isOrthogonal_default_) {
        json.insert("isOrthogonal", isOrthogonal_);
    }
    if (isSimple_ != isSimple_default_) {
        json.insert("isSimple", isSimple_);
    }
    if (isSubmachineState_ != isSubmachineState_default_) {
        json.insert("isSubmachineState", isSubmachineState_);
    }
    if (redefinedState_) {
        QJsonObject redefinedState_json;
        redefinedState_->toJson(redefinedState_json);
        json.insert("redefinedState", redefinedState_json);
    }
    if (redefinitionContext_) {
        QJsonObject redefinitionContext_json;
        redefinitionContext_->toJson(redefinitionContext_json);
        json.insert("redefinitionContext", redefinitionContext_json);
    }
    JsonSerializable::toJsonArray(regions_, "regions", json);
    if (stateInvariant_) {
        QJsonObject stateInvariant_json;
        stateInvariant_->toJson(stateInvariant_json);
        json.insert("stateInvariant", stateInvariant_json);
    }
    if (submachine_) {
        QJsonObject submachine_json;
        submachine_->toJson(submachine_json);
        json.insert("submachine", submachine_json);
    }
}

/// <summary>
/// containingStateMachine
    /// The query containingStateMachine() returns the StateMachine that contains the
    /// State either directly or transitively.
/// </summary>
    /// <code>
    /// result = (container.containingStateMachine())
    /// </code>
shared_ptr<StateMachine> State::containingStateMachine() const
{
    return nullptr;
}

/// <summary>
/// isConsistentWith
    /// The query isConsistentWith() specifies that a redefining State is consistent
    /// with a redefined State provided that the redefining State is an extension of the
    /// redefined State A simple State can be redefined (extended) to become a composite
    /// State (by adding one or more Regions) and a composite State can be redefined
    /// (extended) by adding Regions and by adding Vertices, States, and Transitions to
    /// inherited Regions. All States may add or replace entry, exit, and 'doActivity'
    /// Behaviors.
/// </summary>
    /// <code>
    /// result = (-- the following is merely a default body; it is expected that the specific form of this constraint will be specified by profiles
    /// true)
    /// </code>
    /// <code>
    /// redefiningElement.isRedefinitionContextValid(self)
    /// </code>
bool State::isConsistentWith(shared_ptr<RedefinableElement> redefiningElement) const
{
    return false;
}

/// <summary>
/// isRedefinitionContextValid
    /// The query isRedefinitionContextValid() specifies whether the redefinition
    /// contexts of a State are properly related to the redefinition contexts of the
    /// specified State to allow this element to redefine the other. This means that the
    /// containing Region of a redefining State must redefine the containing Region of
    /// the redefined State.
/// </summary>
    /// <code>
    /// result = (if redefinedElement.oclIsKindOf(State) then
    ///   let redefinedState : State = redefinedElement.oclAsType(State) in
    ///     container.redefinedElement.oclAsType(Region)->exists(r:Region |
    ///       r.subvertex->includes(redefinedState))
    /// else
    ///   false
    /// endif)
    /// </code>
bool State::isRedefinitionContextValid(shared_ptr<RedefinableElement> redefinedElement) const
{
    return false;
}

/// <summary>
/// connections getter
/// The entry and exit connection points used in conjunction with this (submachine)
/// State, i.e., as targets and sources, respectively, in the Region with the
/// submachine State. A connection point reference references the corresponding
/// definition of a connection point Pseudostate in the StateMachine referenced by
/// the submachine State.
/// </summary>
vector<shared_ptr<ConnectionPointReference>>& State::connections() {
    return connections_;
}

/// <summary>
/// connectionPoints getter
/// The entry and exit Pseudostates of a composite State. These can only be entry or
/// exit Pseudostates, and they must have different names. They can only be defined
/// for composite States.
/// </summary>
vector<shared_ptr<Pseudostate>>& State::connectionPoints() {
    return connectionPoints_;
}

/// <summary>
/// deferrableTriggers getter
/// A list of Triggers that are candidates to be retained by the StateMachine if
/// they trigger no Transitions out of the State (not consumed). A deferred Trigger
/// is retained until the StateMachine reaches a State configuration where it is no
/// longer deferred.
/// </summary>
vector<shared_ptr<Trigger>>& State::deferrableTriggers() {
    return deferrableTriggers_;
}

/// <summary>
/// doActivity getter
/// An optional Behavior that is executed while being in the State. The execution
/// starts when this State is entered, and ceases either by itself when done, or
/// when the State is exited, whichever comes first.
/// </summary>
shared_ptr<Behavior> State::doActivity() const {
    return doActivity_;
}

/// <summary>
/// doActivity setter
/// An optional Behavior that is executed while being in the State. The execution
/// starts when this State is entered, and ceases either by itself when done, or
/// when the State is exited, whichever comes first.
/// </summary>
void State::setDoActivity(shared_ptr<Behavior> value) {
    doActivity_ = value;
}

/// <summary>
/// entry getter
/// An optional Behavior that is executed whenever this State is entered regardless
/// of the Transition taken to reach the State. If defined, entry Behaviors are
/// always executed to completion prior to any internal Behavior or Transitions
/// performed within the State.
/// </summary>
shared_ptr<Behavior> State::entry() const {
    return entry_;
}

/// <summary>
/// entry setter
/// An optional Behavior that is executed whenever this State is entered regardless
/// of the Transition taken to reach the State. If defined, entry Behaviors are
/// always executed to completion prior to any internal Behavior or Transitions
/// performed within the State.
/// </summary>
void State::setEntry(shared_ptr<Behavior> value) {
    entry_ = value;
}

/// <summary>
/// exit getter
/// An optional Behavior that is executed whenever this State is exited regardless
/// of which Transition was taken out of the State. If defined, exit Behaviors are
/// always executed to completion only after all internal and transition Behaviors
/// have completed execution.
/// </summary>
shared_ptr<Behavior> State::exit() const {
    return exit_;
}

/// <summary>
/// exit setter
/// An optional Behavior that is executed whenever this State is exited regardless
/// of which Transition was taken out of the State. If defined, exit Behaviors are
/// always executed to completion only after all internal and transition Behaviors
/// have completed execution.
/// </summary>
void State::setExit(shared_ptr<Behavior> value) {
    exit_ = value;
}

/// <summary>
/// isComposite getter
/// A state with isComposite=true is said to be a composite State. A composite State
/// is a State that contains at least one Region.
/// </summary>
bool State::isComposite() const {
    return isComposite_;
}

/// <summary>
/// isComposite setter
/// A state with isComposite=true is said to be a composite State. A composite State
/// is a State that contains at least one Region.
/// </summary>
void State::setIsComposite(bool value) {
    isComposite_ = value;
}

/// <summary>
/// isOrthogonal getter
/// A State with isOrthogonal=true is said to be an orthogonal composite State An
/// orthogonal composite State contains two or more Regions.
/// </summary>
bool State::isOrthogonal() const {
    return isOrthogonal_;
}

/// <summary>
/// isOrthogonal setter
/// A State with isOrthogonal=true is said to be an orthogonal composite State An
/// orthogonal composite State contains two or more Regions.
/// </summary>
void State::setIsOrthogonal(bool value) {
    isOrthogonal_ = value;
}

/// <summary>
/// isSimple getter
/// A State with isSimple=true is said to be a simple State A simple State does not
/// have any Regions and it does not refer to any submachine StateMachine.
/// </summary>
bool State::isSimple() const {
    return isSimple_;
}

/// <summary>
/// isSimple setter
/// A State with isSimple=true is said to be a simple State A simple State does not
/// have any Regions and it does not refer to any submachine StateMachine.
/// </summary>
void State::setIsSimple(bool value) {
    isSimple_ = value;
}

/// <summary>
/// isSubmachineState getter
/// A State with isSubmachineState=true is said to be a submachine State Such a
/// State refers to another StateMachine(submachine).
/// </summary>
bool State::isSubmachineState() const {
    return isSubmachineState_;
}

/// <summary>
/// isSubmachineState setter
/// A State with isSubmachineState=true is said to be a submachine State Such a
/// State refers to another StateMachine(submachine).
/// </summary>
void State::setIsSubmachineState(bool value) {
    isSubmachineState_ = value;
}

/// <summary>
/// redefinedState getter
/// The State of which this State is a redefinition.
/// </summary>
shared_ptr<State> State::redefinedState() const {
    return redefinedState_;
}

/// <summary>
/// redefinedState setter
/// The State of which this State is a redefinition.
/// </summary>
void State::setRedefinedState(shared_ptr<State> value) {
    redefinedState_ = value;
}

/// <summary>
/// redefinitionContext getter
/// References the Classifier in which context this element may be redefined.
/// </summary>
shared_ptr<Classifier> State::redefinitionContext() const {
    return redefinitionContext_;
}

/// <summary>
/// redefinitionContext setter
/// References the Classifier in which context this element may be redefined.
/// </summary>
void State::setRedefinitionContext(shared_ptr<Classifier> value) {
    redefinitionContext_ = value;
}

/// <summary>
/// regions getter
/// The Regions owned directly by the State.
/// </summary>
vector<shared_ptr<Region>>& State::regions() {
    return regions_;
}

/// <summary>
/// stateInvariant getter
/// Specifies conditions that are always true when this State is the current State.
/// In ProtocolStateMachines state invariants are additional conditions to the
/// preconditions of the outgoing Transitions, and to the postcondition of the
/// incoming Transitions.
/// </summary>
shared_ptr<Constraint> State::stateInvariant() const {
    return stateInvariant_;
}

/// <summary>
/// stateInvariant setter
/// Specifies conditions that are always true when this State is the current State.
/// In ProtocolStateMachines state invariants are additional conditions to the
/// preconditions of the outgoing Transitions, and to the postcondition of the
/// incoming Transitions.
/// </summary>
void State::setStateInvariant(shared_ptr<Constraint> value) {
    stateInvariant_ = value;
}

/// <summary>
/// submachine getter
/// The StateMachine that is to be inserted in place of the (submachine) State.
/// </summary>
shared_ptr<StateMachine> State::submachine() const {
    return submachine_;
}

/// <summary>
/// submachine setter
/// The StateMachine that is to be inserted in place of the (submachine) State.
/// </summary>
void State::setSubmachine(shared_ptr<StateMachine> value) {
    submachine_ = value;
}

} // namespace uml
